﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using ch.ethz.ssh2;

namespace FormsTest
{
    public partial class LoginMain : Form
    {
        string _hostName = "192.168.110.96";
        string _userName = "root";
        public LoginMain()
        {
            InitializeComponent();
        }

        private void loginButton_Click(object sender, EventArgs e)
        {
            System.String hostname = hostField.Text.Trim();
            System.String username = userField.Text.Trim();
            if ((hostname.Length == 0) || (username.Length == 0))
            {
                SafeShowMessage(this, "Please fill out both fields!", "Message", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
                return;
            }

            loginButton.Enabled = false;
            hostField.Enabled = false;
            userField.Enabled = false;

            new Thread(new ThreadStart(Run)).Start();
            Hide();
        }

        public void Run()
        {
            System.String hostname = hostField.Text.Trim();
            System.String username = userField.Text.Trim();
            Connection conn = new Connection(hostname);

            try
            {
                /*
                * 
                * CONNECT AND VERIFY SERVER HOST KEY (with callback)
                * 
                */

                System.String[] hostkeyAlgos = this.database.getPreferredServerHostkeyAlgorithmOrder(hostname);

                if (hostkeyAlgos != null)
                    conn.ServerHostKeyAlgorithms = hostkeyAlgos;

                conn.connect(new AdvancedVerifier(this));

                /*
                * 
                * AUTHENTICATION PHASE
                * 
                */

                bool enableKeyboardInteractive = true;
                bool enableDSA = true;
                bool enableRSA = true;

                System.String lastError = null;

                while (true)
                {
                    if ((enableDSA || enableRSA) && conn.isAuthMethodAvailable(username, "publickey"))
                    {
                        if (enableDSA)
                        {
                            System.IO.FileInfo key = new System.IO.FileInfo(LoginMain.idDSAPath);

                            bool tmpBool;
                            if (System.IO.File.Exists(key.FullName))
                                tmpBool = true;
                            else
                                tmpBool = System.IO.Directory.Exists(key.FullName);
                            if (tmpBool)
                            {
                                EnterSomethingDialog esd = new EnterSomethingDialog(this, "DSA Authentication", new System.String[] { lastError, "Enter DSA private key password:" }, true);
                                //UPGRADE_TODO: Method 'java.awt.Component.setVisible' was converted to 'System.Windows.Forms.Control.Visible' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaawtComponentsetVisible_boolean'"
                                //UPGRADE_TODO: 'System.Windows.Forms.Application.Run' must be called to start a main form. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1135'"
                                SafeShowDialog(this, esd);

                                bool res = conn.authenticateWithPublicKey(username, key, esd.answer);

                                if (res == true)
                                    break;

                                lastError = "DSA authentication failed.";
                            }
                            enableDSA = false; // do not try again
                        }

                        if (enableRSA)
                        {
                            System.IO.FileInfo key = new System.IO.FileInfo(LoginMain.idRSAPath);

                            bool tmpBool2;
                            if (System.IO.File.Exists(key.FullName))
                                tmpBool2 = true;
                            else
                                tmpBool2 = System.IO.Directory.Exists(key.FullName);
                            if (tmpBool2)
                            {
                                EnterSomethingDialog esd = new EnterSomethingDialog(this, "RSA Authentication", new System.String[] { lastError, "Enter RSA private key password:" }, true);
                                //UPGRADE_TODO: Method 'java.awt.Component.setVisible' was converted to 'System.Windows.Forms.Control.Visible' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaawtComponentsetVisible_boolean'"
                                //UPGRADE_TODO: 'System.Windows.Forms.Application.Run' must be called to start a main form. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1135'"
                                SafeShowDialog(this, esd);

                                bool res = conn.authenticateWithPublicKey(username, key, esd.answer);

                                if (res == true)
                                    break;

                                lastError = "RSA authentication failed.";
                            }
                            enableRSA = false; // do not try again
                        }

                        continue;
                    }

                    if (enableKeyboardInteractive && conn.isAuthMethodAvailable(username, "keyboard-interactive"))
                    {
                        InteractiveLogic il = new InteractiveLogic(this, lastError);

                        bool res = conn.authenticateWithKeyboardInteractive(username, il);

                        if (res == true)
                            break;

                        if (il.PromptCount == 0)
                        {
                            // aha. the server announced that it supports "keyboard-interactive", but when
                            // we asked for it, it just denied the request without sending us any prompt.
                            // That happens with some server versions/configurations.
                            // We just disable the "keyboard-interactive" method and notify the user.

                            lastError = "Keyboard-interactive does not work.";

                            enableKeyboardInteractive = false; // do not try this again
                        }
                        else
                        {
                            lastError = "Keyboard-interactive auth failed."; // try again, if possible
                        }

                        continue;
                    }

                    if (conn.isAuthMethodAvailable(username, "password"))
                    {
                        //UPGRADE_NOTE: Final was removed from the declaration of 'esd '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
                        EnterSomethingDialog esd = new EnterSomethingDialog(this, "Password Authentication", new System.String[] { lastError, "Enter password for " + username }, true);

                        //UPGRADE_TODO: Method 'java.awt.Component.setVisible' was converted to 'System.Windows.Forms.Control.Visible' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaawtComponentsetVisible_boolean'"
                        //UPGRADE_TODO: 'System.Windows.Forms.Application.Run' must be called to start a main form. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1135'"
                        //SafeVisible(this, esd, true);
                        SafeShowDialog(this, esd);

                        if (esd.answer == null)
                            throw new System.IO.IOException("Login aborted by user");

                        bool res = conn.authenticateWithPassword(username, esd.answer);

                        if (res == true)
                            break;

                        lastError = "Password authentication failed."; // try again, if possible

                        continue;
                    }

                    throw new System.IO.IOException("No supported authentication methods available.");
                }

                /*
                * 
                * AUTHENTICATION OK. DO SOMETHING.
                * 
                */

                Session sess = conn.openSession();

                int term_width = 90;
                int term_height = 30;

                sess.requestPTY("dumb", term_width, term_height, 0, 0, null);
                sess.startShell();

                TerminalWin td = new TerminalWin(this, username + "@" + hostname, sess, term_width, term_height);
                //TerminalDialog td = new TerminalDialog(this, username + "@" + hostname, sess, term_width, term_height);

                /* The following call blocks until the dialog has been closed */

                //UPGRADE_TODO: Method 'java.awt.Component.setVisible' was converted to 'System.Windows.Forms.Control.Visible' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaawtComponentsetVisible_boolean'"
                //UPGRADE_TODO: 'System.Windows.Forms.Application.Run' must be called to start a main form. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1135'"
                //SafeShowDialog(this, td);
            }
            catch (System.IO.IOException e)
            {
                SafeShowMessage(this, "Exception: " + e.Message, "Message", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Information);
            }

            /*
            * 
            * CLOSE THE CONNECTION.
            * 
            */

            conn.close();

            /*
            * 
            * CLOSE THE LOGIN FRAME - APPLICATION WILL BE EXITED (no more frames)
            * 
            */

            //IThreadRunnable r = new AnonymousClassRunnable1(this);
            Environment.Exit(0);

            //UPGRADE_ISSUE: Method 'javax.swing.SwingUtilities.invokeLater' was not converted. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1000_javaxswingSwingUtilities'"
            //SwingUtilities.invokeLater(r);
        }

        /// <summary> This ServerHostKeyVerifier asks the user on how to proceed if a key cannot be found
        /// in the in-memory database.
        /// 
        /// </summary>
        internal class AdvancedVerifier : ServerHostKeyVerifier
        {
            public AdvancedVerifier(LoginMain loginMain)
            {
                this.loginMain = loginMain;
            }
            private LoginMain loginMain;
            public virtual bool verifyServerHostKey(System.String hostname, int port, System.String serverHostKeyAlgorithm, byte[] serverHostKey)
            {
                //UPGRADE_NOTE: Final was removed from the declaration of 'host '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
                System.String host = hostname;
                //UPGRADE_NOTE: Final was removed from the declaration of 'algo '. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1003'"
                System.String algo = serverHostKeyAlgorithm;

                System.String message;

                /* Check database */

                int result = loginMain.database.verifyHostkey(hostname, serverHostKeyAlgorithm, serverHostKey);

                switch (result)
                {

                    case KnownHosts.HOSTKEY_IS_OK:
                        return true;


                    case KnownHosts.HOSTKEY_IS_NEW:
                        message = "Do you want to accept the hostkey (type " + algo + ") from " + host + " ?\n";
                        break;


                    case KnownHosts.HOSTKEY_HAS_CHANGED:
                        message = "WARNING! Hostkey for " + host + " has changed!\nAccept anyway?\n";
                        break;


                    default:
                        throw new System.SystemException();

                }

                /* Include the fingerprints in the message */

                System.String hexFingerprint = KnownHosts.createHexFingerprint(serverHostKeyAlgorithm, serverHostKey);
                System.String bubblebabbleFingerprint = KnownHosts.createBubblebabbleFingerprint(serverHostKeyAlgorithm, serverHostKey);

                message += ("Hex Fingerprint: " + hexFingerprint + "\nBubblebabble Fingerprint: " + bubblebabbleFingerprint);

                /* Now ask the user */

                DialogResult choice = SafeShowMessage(loginMain, message, "Select an option...", System.Windows.Forms.MessageBoxButtons.YesNoCancel, System.Windows.Forms.MessageBoxIcon.Question);

                //UPGRADE_TODO: The equivalent in .NET for field 'javax.swing.JOptionPane.YES_OPTION' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                if (choice == System.Windows.Forms.DialogResult.Yes)
                {
                    /* Be really paranoid. We use a hashed hostname entry */

                    System.String hashedHostname = KnownHosts.createHashedHostname(hostname);

                    /* Add the hostkey to the in-memory database */

                    loginMain.database.addHostkey(new System.String[] { hashedHostname }, serverHostKeyAlgorithm, serverHostKey);

                    /* Also try to add the key to a known_host file */

                    try
                    {
                        KnownHosts.addHostkeyToFile(new System.IO.FileInfo(LoginMain.knownHostPath), new System.String[] { hashedHostname }, serverHostKeyAlgorithm, serverHostKey);
                    }
                    catch (System.IO.IOException ignore)
                    {
                    }

                    return true;
                }

                //UPGRADE_TODO: The equivalent in .NET for field 'javax.swing.JOptionPane.CANCEL_OPTION' may return a different value. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1043'"
                if (choice == System.Windows.Forms.DialogResult.Cancel)
                {
                    throw new System.Exception("The user aborted the server hostkey verification.");
                }

                return false;
            }
        }

        /// <summary> The logic that one has to implement if "keyboard-interactive" autentication shall be
        /// supported.
        /// 
        /// </summary>
        internal class InteractiveLogic : InteractiveCallback
        {
            private LoginMain loginMain;

            /* We maintain a prompt counter - this enables the detection of situations where the ssh
             * server is signaling "authentication failed" even though it did not send a single prompt.
             */
            virtual public int PromptCount
            {

                get
                {
                    return promptCount;
                }

            }
            internal int promptCount = 0;
            internal System.String lastError;

            public InteractiveLogic(LoginMain loginMain, System.String lastError)
            {
                this.loginMain = loginMain;
                this.lastError = lastError;
            }

            /* the callback may be invoked several times, depending on how many questions-sets the server sends */

            public virtual System.String[] replyToChallenge(System.String name, System.String instruction, int numPrompts, System.String[] prompt, bool[] echo)
            {
                System.String[] result = new System.String[numPrompts];

                for (int i = 0; i < numPrompts; i++)
                {
                    /* Often, servers just send empty strings for "name" and "instruction" */

                    System.String[] content = new System.String[] { lastError, name, instruction, prompt[i] };

                    if (lastError != null)
                    {
                        /* show lastError only once */
                        lastError = null;
                    }

                    EnterSomethingDialog esd = new EnterSomethingDialog(loginMain, "Keyboard Interactive Authentication", content, !echo[i]);

                    //UPGRADE_TODO: Method 'java.awt.Component.setVisible' was converted to 'System.Windows.Forms.Control.Visible' which has a different behavior. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1073_javaawtComponentsetVisible_boolean'"
                    //UPGRADE_TODO: 'System.Windows.Forms.Application.Run' must be called to start a main form. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1135'"
                    SafeShowDialog(loginMain, esd);

                    if (esd.answer == null)
                        throw new System.IO.IOException("Login aborted by user");

                    result[i] = esd.answer;
                    promptCount++;
                }

                return result;
            }
        }

        private static DialogResult SafeShowMessage(Control owner, string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon)
        {
            DialogResult result;
            if (owner.InvokeRequired)
            {
                System.Func<string, string, MessageBoxButtons, MessageBoxIcon, DialogResult> fun = new Func<string, string, MessageBoxButtons, MessageBoxIcon, DialogResult>(System.Windows.Forms.MessageBox.Show);
                result = (DialogResult)owner.Invoke(fun, text, caption, buttons, icon);
            }
            else
                result = System.Windows.Forms.MessageBox.Show(owner, text, caption, buttons, icon);
            return result;
        }

        private static DialogResult SafeShowDialog(Control owner, Form control)
        {
            DialogResult result;
            if (owner.InvokeRequired)
            {
                System.Func<Control, DialogResult> fun = new Func<Control, DialogResult>(control.ShowDialog);
                result = (DialogResult)owner.Invoke(fun, owner);
            }
            else
            {
                result = control.ShowDialog(owner);
            }
            return result;
        }

        private static bool SafeVisible(Control owner, Control control, bool visible)
        {
            bool result;
            if (owner.InvokeRequired)
            {
                System.Func<bool, bool> fun = delegate(bool v)
                {
                    return control.Visible = v;
                };
                result = (bool)owner.Invoke(fun, visible);
            }
            else
            {
                result = control.Visible = visible;
            }
            return result;
        }

        /* 
        * NOTE: to get this feature to work, replace the "tilde" with your home directory,
        * at least my JVM does not understand it. Need to check the specs.
        */
        internal const System.String knownHostPath = "~/.ssh/known_hosts";
        internal const System.String idDSAPath = "~/.ssh/id_dsa";
        internal const System.String idRSAPath = "~/.ssh/id_rsa";

        internal KnownHosts database = new KnownHosts();
    }
}
